
/**
 * 材料/资源
 */
class Item {
    private readonly _name: string;

    constructor(name: string) {
        this._name = name;
    }

    get name(): string {
        return this._name;
    }
}

/**
 * 消耗资源
 */
class Input {

    private readonly _item: Item;
    private readonly _count: number;

    /**
     * 
     * @param item 资源
     * @param count 数量
     */
    constructor(item: Item, count: number) {
        this._item = item;
        this._count = count;
    }

    get item(): Item {
        return this._item;
    }
    get count(): number {
        return this._count;
    }
}

/**
 * 产出物
 */
class Output {

    private readonly _item: Item;
    private readonly _count: number;

    /**
     * 
     * @param item 材料
     * @param count 数量
     */
    constructor(item: Item, count: number) {
        this._item = item;
        this._count = count;
    }

    get item(): Item {
        return this._item;
    }
    get count(): number {
        return this._count;
    }
}

/**
 * 关卡
 */
class Mission {
    private readonly _name: string;
    private readonly _input: Array<Input>;
    private readonly _output: Array<Output>;

    /**
     * 
     * @param name 关卡名称
     * @param input 消耗资源
     * @param output 产出物
     */
    constructor(name: string, input: Array<Input>, output: Array<Output>) {
        this._name = name;
        this._input = (input ?? []).filter(o => o.count > 0);
        this._output = (output ?? []).filter(o => o.count > 0);
    }

    get name(): string {
        return this._name;
    }
    get input(): Readonly<Array<Input>> {
        return this._input ?? [];
    }
    get output(): Readonly<Array<Output>> {
        return this._output ?? [];
    }

    private _fom?: Map<string, number>;
    flatOutput(): Map<string, number> {
        if (this._fom) {
            return this._fom;
        }

        const map = new Map<string, number>();
        for (let o of this.output) {
            map.set(o.item.name, o.count);
        }
        return this._fom = map;
    }
    provide(name: string): number {
        return this.flatOutput().get(name) ?? 0;
    }
    multiple(): number {
        return this.flatOutput().size;
    }
}

/**
 * 计算结果
 */
type CalcResult = {
    input: Array<Input>; //总共消耗资源列表
    output: Array<Output>; //总产出列表
    mission: Map<Mission, number> //关卡扫荡次数
};

/**
 * 计算器
 */
class Calculator {
    private readonly _mission: Array<Mission>;
    /**
     * 
     * @param mission 关卡列表
     */
    constructor(mission: Array<Mission>) {
        this._mission = mission;
    }
    get mission(): Readonly<Array<Mission>> {
        return this._mission ?? [];
    }

    /**
     * 计算
     * @param output 需要的产出物, 重复的会被覆盖
     */
    calc(output: Array<Output>): CalcResult {
        const stat = new Map<string, {
            name: string; //产出物名称
            count: number; //产出物需要的数量
            mission: Array<Mission>; //可以提供产出物的关卡列表
        }>();
        //先统计一遍
        for (let o of (output ?? [])) {
            const n = o.item.name;
            const v = {
                name: n,
                count: o.count,
                mission: new Array<Mission>()
            };
            stat.set(n, v);
            for (let m of this.mission) {
                if (m.provide(n) > 0 && !v.mission.includes(m)) {
                    v.mission.push(m);
                }
            }
            //排序关卡, 首项默认为最优解, 按照: 最多产出 > 相同数量时, 产出物种类最多 排序, 此处可提供多种策略, 如按照 产出/资源 比例等等
            v.mission
                .sort((m0, m1) => (m1.provide(n) - m0.provide(n)) || (m1.multiple() - m0.multiple()))
        }
        //console.log(stat);

        //记录关卡和扫荡次数映射
        const cache = new Map<Mission, number>();

        //实际计算
        [...stat.values()]
            .filter(o => o.mission.length > 0) //过滤掉没有产出关卡的
            .sort((a, b) => b.mission[0].multiple() - a.mission[0].multiple()) //先计算最优解中产出种类最多的, 因为它影响最大
            .forEach(o => {
                const n = o.name, m = o.mission[0];
                const onceCount = m.provide(n);
                let total = o.count;

                //处理关卡关联产出的情况
                for (let [m, c] of cache) {
                    const mc = m.provide(n);
                    if (mc > 0) {
                        total -= c * mc;
                    }
                }

                if (total > 0) {
                    const times = Math.ceil(total / onceCount);//向上取整
                    cache.set(m, (cache.get(m) ?? 0) + times);
                }
            });

        //console.info(cache);

        const im = new Map<Item, number>();
        const om = new Map<Item, number>();
        for (let [m, c] of cache) {
            m.input.forEach(v => {
                im.set(v.item, (im.get(v.item) ?? 0) + v.count * c);
            });
            m.output.forEach(v => {
                om.set(v.item, (om.get(v.item) ?? 0) + v.count * c);
            });
        }
        const ia = new Array<Input>();
        for (let [i, c] of im) {
            ia.push(new Input(i, c));
        }
        const oa = new Array<Output>();
        for (let [i, c] of om) {
            oa.push(new Output(i, c));
        }
        return {
            input: ia,
            output: oa,
            mission: cache
        }
    }
}

namespace CalcResult {
    export function dump(ret: CalcResult) {
        const info = console.info.bind(console);
        info(`需要消耗以下资源:`);
        for (let o of ret.input) {
            info(`  ${o.item.name} = ${o.count}`);
        }
        info(`产出:`);
        for (let o of ret.output) {
            info(`  ${o.item.name} = ${o.count}`);
        }
        info(`详情:`);
        for (let [m, c] of ret.mission) {
            info(`  ${m.name} 刷 ${c} 次, 产出:`);
            for (let [n, v] of m.flatOutput()) {
                info(`    ${n} = ${v * c}`);
            }
        }
    }
}

function sister() {
    const ti = new Item('体力');
    const fu = new Item('符');
    const q = new Item('铅');
    const b = new Item('碧');
    const y = new Item('熠');

    const m0 = new Mission('连理枝', [new Input(ti, 20)], [new Output(q, 3)]);
    const m1 = new Mission('启明慧', [new Input(ti, 20)], [new Output(b, 3)]);
    const m2 = new Mission('消业火', [new Input(ti, 20)], [new Output(q, 4)]);
    const m3 = new Mission('两分钗', [new Input(ti, 20)], [new Output(b, 4)]);
    const m4 = new Mission('静思雪', [new Input(ti, 20)], [new Output(y, 3)]);
    const m5 = new Mission('乍还身', [new Input(ti, 20)], [new Output(q, 2), new Output(b, 2)]);
    const m6 = new Mission('彼岸香', [new Input(ti, 20)], [new Output(y, 4)]);

    const calculator = new Calculator([m0, m1, m2, m3, m4, m5, m6]);
    const ret = calculator.calc([new Output(q, 184 - 12), new Output(b, 184 - 12), new Output(y, 40 - 21)]);
    CalcResult.dump(ret);
}

sister();

/*
编译TS:  tsc ./task.ts --target ES5 --downlevelIteration
运行JS:  node ./task.js

暂时没有时间写页面, 有空了弄一个出来
*/

